﻿using Microsoft.Scripting.Ast;
using Scala.Compiler.Binding;
using Scala.Compiler.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Scala.Compiler.Ast
{
    public class ApplyExpression : Expression
    {
        private readonly Expression _target;
        private readonly IList<Arg> _args;

        public ApplyExpression(Expression target, IList<Arg> args)
        {
            _target = target;
            _args = args;
        }

        public Expression Target { get { return _target; } }
        public IList<Arg> Args { get { return _args; } }

        public override void Walk(ScalaWalker walker)
        {
            if (walker.Walk(this))
            {
                if (_target != null)
                {
                    _target.Walk(walker);
                }
                if (_args != null)
                {
                    foreach (Arg arg in _args)
                    {
                        arg.Walk(walker);
                    }
                }
            }
            walker.PostWalk(this);
        }

        public override Microsoft.Scripting.Ast.Expression Reduce()
        {
            IList<Microsoft.Scripting.Ast.Expression> args = new List<Microsoft.Scripting.Ast.Expression>();
            IList<Type> paramTypes = new List<Type>();

            foreach (var x in Args)
            {
                args.Add(x.Reduce());
                Type t;
                if (!InternalTypes.GetType(x.TypeName, Scope, out t)) throw new Exception(String.Format("Couldn't Resolve the Type {0} of the argument {1} in the function call in the Scope {2}", x.TypeName, x.Name, Scope.Name));
                paramTypes.Add(t);
            }

            if (Target is SelectStatement)
            {
                Target.Reduce();
                Microsoft.Scripting.Ast.Expression instance = null;
                var sel = Target as SelectStatement;
                if (sel.Prop != null)
                {
                    instance = Expression.TypeMethodCall(
                                    sel.Prop.DeclaringType,
                                    sel.Prop.GetGetMethod(true),
                                    Type.EmptyTypes,
                                    new ParameterExpression[] { }
                                );
                }
                else if (sel.QualExpr != null)
                {
                    instance = sel.QualExpr;
                }
                //if (tempTarget.Meth == null)
                //{
                //    if (tempTarget.Prop == null)
                //    {
                //        throw new Exception(String.Format("Not found Method or Property {0} of Type {1}", tempTarget.ExprName, tempTarget.Qual));
                //    }
                //    return Expression.TypeMethodCall(tempTarget.ScalaType.TypeBuilder.AsType(), tempTarget.Prop.GetGetMethod(true), paramTypes.ToArray(), args.ToArray());
                //}
                //if (tempTarget.QualExpr != null)
                //{
                //    return Expression.TypeMethodCall(tempTarget.QualExpr, tempTarget.Meth, paramTypes.ToArray(), args.ToArray());
                //}
                //else if (tempTarget.Prop != null)
                //{
                //    return Expression.TypeMethodCall(tempTarget.)
                //}
                if (sel.IsMeth)
                {
                    if (instance == null)
                    {
                        return Expression.TypeMethodCall(sel.Meth.DeclaringType, sel.Meth, paramTypes.ToArray(), args.ToArray());
                    }
                    else
                    {
                        return Expression.TypeMethodCall(instance, sel.Meth, paramTypes.ToArray(), args.ToArray());
                    }
                }
                else if (sel.IsProp)
                {
                    if (instance == null)
                    {
                        return Expression.TypeMethodCall(sel.RetProp.DeclaringType, sel.RetProp.GetGetMethod(true), paramTypes.ToArray(), args.ToArray());
                    }
                    else
                    {
                        return Expression.TypeMethodCall(instance, sel.RetProp.GetGetMethod(true), paramTypes.ToArray(), args.ToArray());
                    }
                }
                else
                {
                    throw new Exception(String.Format("The assignment must be a method or property"));
                }
            }
            if (Target is NameExpression)
            {
                var tar = Target as NameExpression;
                ScalaFunction func;
                if (Scope.TryGetAnyFunc(tar.Name, out func))
                {
                    if (func.ParamTypes.Length == args.Count)
                    {
                        return Expression.TypeMethodCall(
                                        func.MethodBuilder.DeclaringType,
                                        func.MethodBuilder,
                                        func.ParamTypes,
                                        args.ToArray()
                                        );
                    }
                    else
                    {
                        throw new Exception(String.Format("Correct Number of Arguments not supplied for the function {0}", func.Name));
                    }
                }
            }
            throw new Exception(String.Format("Improper Function call"));
        }
    }
}
